package org.dsapr.codegen.processor.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.dsapr.codegen.common.AutoGenerateController;
import org.dsapr.codegen.common.BaseGenerateProcessor;
import org.dsapr.codegen.common.CodeGenerateProcessor;
import org.dsapr.codegen.model.NameContext;
import org.dsapr.codegen.model.PageRequestWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.File;
import java.lang.annotation.Annotation;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static org.dsapr.codegen.common.Method.C;
import static org.dsapr.codegen.common.Method.CRUD;
import static org.dsapr.codegen.utils.NameUtil.toCamelCase;

@AutoService(value = CodeGenerateProcessor.class)
public class ControllerAutoGenerateProcessor extends BaseGenerateProcessor {
	private static final String CODEGEN_INFO = buildMessage();

	/**
	 * 生成类文件
	 */
	@Override
	public void generateClass(TypeElement typeElement, RoundEnvironment roundEnvironment) {
		NameContext context = NameContext.builder().build(typeElement);
		if (context.getServiceClassName() == null) return;
		FieldSpec serviceField = FieldSpec
				.builder(ClassName.get(context.getControllerPackageName(), context.getIServiceClassName()), toCamelCase(context.getServiceClassName()))
				.addModifiers(Modifier.PRIVATE)
				.addAnnotation(Autowired.class)
				.build();
		TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(context.getControllerClassName())
				.addAnnotation(Slf4j.class)
				.addAnnotation(RestController.class)
				.addJavadoc(CODEGEN_INFO)
				.addAnnotation(AnnotationSpec.builder(RequestMapping.class)
						.addMember("value", "$S", UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString())).build())
				.addModifiers(Modifier.PUBLIC)
				.addField(serviceField);

		// 添加方法
		if (Arrays.stream(context.getControllerMethod())
				.allMatch(method -> method.equals(C)
						|| method.equals(CRUD)))
			typeSpecBuilder.addMethod(getCreateMethodSpec(context));

		// 分页查询
		typeSpecBuilder.addMethod(getPageListMethodSpec(context));

		// 列表查询
		typeSpecBuilder.addMethod(getListMethodSpec(context));

		// request innerClass
		String requestClassName = context.getClassName() + "Request";
		TypeSpec requestNestedClassType = TypeSpec.classBuilder(requestClassName)
				.addAnnotation(Data.class).build();
		typeSpecBuilder.addType(requestNestedClassType);

		// innerClass
		TypeSpec nestedClassType = TypeSpec.classBuilder("PageRequestAttribute")
				.superclass(ParameterizedTypeName.get(ClassName.get(PageRequestWrapper.class), ClassName.get(context.getControllerFullyQualifiedName(), context.getClassName() + "Request"))).build();
		typeSpecBuilder.addType(nestedClassType);

		Path srcPath = Paths.get(typeElement.getAnnotation(AutoGenerateController.class).sourcePath());
		File srcAbsoluteFile = srcPath.toFile().getAbsoluteFile();
		if (srcAbsoluteFile.exists()) {
			File sourceFile = new File(srcAbsoluteFile.getAbsolutePath(), context.getControllerClassName() + ".java");
			if (!sourceFile.exists()) {
				writeJavaFile(getPackage(typeElement), typeSpecBuilder.build(), srcAbsoluteFile.getAbsolutePath());
			}
		}
	}

	private MethodSpec getCreateMethodSpec(NameContext context) {
		return MethodSpec.methodBuilder("create" + context.getClassName())
				.addAnnotation(PostMapping.class)
				.addModifiers(Modifier.PUBLIC)
				.returns(ClassName.get(context.getEntityPackageName(), context.getClassName()))
				.addParameter(ClassName.get(context.getEntityPackageName(), context.getClassName()),
						toCamelCase(context.getClassName()))
				.addStatement(
						"$L.save($L)",
						toCamelCase(context.getServiceClassName()), toCamelCase(context.getClassName())
				)
				.addStatement(
						"return $L",
						toCamelCase(context.getClassName())
				)
				.build();
	}

	private MethodSpec getPageListMethodSpec(NameContext context) {
		ParameterSpec parameter = ParameterSpec.builder(ClassName.get(context.getControllerPackageName() + "." + context.getControllerClassName(), "PageRequestAttribute"), "req")
				.addAnnotation(ModelAttribute.class)
				.build();
		return MethodSpec.methodBuilder("page")
				.addAnnotation(AnnotationSpec.builder(GetMapping.class).addMember("value", "$S", "page").build())
				.addModifiers(Modifier.PUBLIC)
				.addParameter(parameter)
				.addCode(CodeBlock.of(
						"$T<$T> query = new QueryWrapper<>();\n",
						ClassName.get(QueryWrapper.class),
						ClassName.get(context.getEntityPackageName(), context.getClassName())
				))
				.addCode("\n")
				.addCode(CodeBlock.of(
						"if (req.hasBean()) {}\n"
				))
				.addCode("\n")
				.addCode(CodeBlock.of(
						"req.ifHasAscColumn(column -> query.orderByAsc(column != null, column));\n" +
								"req.ifHasDescColumn(column -> query.orderByDesc(column != null, column));\n"
				))
				.addCode("\n")
				.addCode(CodeBlock.of(
						"return $L.page($T.of(req.getPage(), req.getPageSize()), query).convert($T.INSTANCE::toVo);",
						toCamelCase(context.getServiceClassName()),
						ClassName.get(Page.class),
						ClassName.get(context.getConverterPackageName(), context.getConverterName())
				))
				.returns(ParameterizedTypeName.get(ClassName.get(IPage.class), ClassName.get(context.getVoPackageName(), context.getVoClassName())))
				.build();
	}

	private MethodSpec getListMethodSpec(NameContext context) {
		String controllerQualifiedName = context.getControllerFullyQualifiedName();
		String reqClassName = context.getClassName() + "Request";
		ParameterSpec parameter = ParameterSpec.builder(ClassName.get(controllerQualifiedName, reqClassName), "req")
				.addAnnotation(RequestParam.class)
				.build();
		return MethodSpec.methodBuilder("list")
				.addAnnotation(GetMapping.class)
				.addModifiers(Modifier.PUBLIC)
				.addParameter(parameter)
				.addCode(CodeBlock.of(
						"$T<$T> query = new QueryWrapper<>();\n",
						ClassName.get(QueryWrapper.class),
						ClassName.get(context.getEntityPackageName(), context.getClassName())
				))
				.addCode("\n")
				.addCode(CodeBlock.of(
						"return $L.list(query).stream().map($T.INSTANCE::toVo).collect($T.toList());",
						toCamelCase(context.getServiceClassName()),
						ClassName.get(context.getConverterPackageName(), context.getConverterName()),
						ClassName.get(Collectors.class)
				))
				.returns(ParameterizedTypeName.get(ClassName.get(List.class), ClassName.get(context.getVoPackageName(), context.getVoClassName())))
				.build();
	}

	@Override
	public Class<? extends Annotation> getAnnotation() {
		return AutoGenerateController.class;
	}

	@Override
	public String getPackage(TypeElement typeElement) {
		return typeElement.getAnnotation(AutoGenerateController.class).packageName();
	}

}
